


#include "MovableInteractionComponent.h"

#include "MovableActorBase.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetSystemLibrary.h"

UMovableInteractionComponent::UMovableInteractionComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
	
}

void UMovableInteractionComponent::BeginPlay()
{
	Super::BeginPlay();

	Owner = CastChecked<APawn>(GetOwner());
	if (Owner.IsValid())
		PlayerPC = CastChecked<APlayerController>(Owner->GetController());
	
}

void UMovableInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// 如果有 MovableActor 被选中
	if (SelectedMovableActor.IsValid())
	{
		// 1. 更新控制手柄屏幕空间大小
		UpdateHandleSizeOnScreen();

		// 2. 如果开始拖拽 或 检测悬浮控制手柄
		if (bIsDragging)
		{
			switch (SelectedMovableActor->GetHandleTranformMode(CurrentHoverCmp.Get()))
			{
			case ETransformMode::ETM_Translation:
				DoTranslate();
				break;

			case ETransformMode::ETM_Rotation:
				DoRotate();
				break;

			case ETransformMode::ETM_Scale:
				DoScale();
				break;
			}
		}
		else
		{
			// 检测被悬浮的控制手柄
			DetectHoveredHandle();
		}
	}
}

void UMovableInteractionComponent::DetectMovableActor(bool Debug)
{
	if (CurrentHoverCmp.IsValid())
		return;
	
	if (LastSelectedMovableActor.IsValid())
	{
		LastSelectedMovableActor->SetControlHandleVisible(false, ETransformMode::ETM_Translation);
		LastSelectedMovableActor->SetControlHandleVisible(false, ETransformMode::ETM_Rotation);
		LastSelectedMovableActor->SetControlHandleVisible(false, ETransformMode::ETM_Scale);
	}
	
	FVector MouseWorldPos, MouseWorldDir;
	if (GetMouseWorldPositionDirection(MouseWorldPos, MouseWorldDir))
	{
		EDrawDebugTrace::Type DrawDebug;
		if (Debug)
			DrawDebug = EDrawDebugTrace::Type::ForDuration;
		else
			DrawDebug = EDrawDebugTrace::Type::None;

		FHitResult HitResult;
		UKismetSystemLibrary::LineTraceSingleForObjects(
			GetWorld(),
			MouseWorldPos,
			MouseWorldPos + DetectDistance*MouseWorldDir,
			TArray<TEnumAsByte<EObjectTypeQuery>>{EObjectTypeQuery::ObjectTypeQuery2},
			false,
			TArray<AActor*>{GetOwner()},
			DrawDebug,
			HitResult,
			true,
			FLinearColor::Red,
			FLinearColor::Green,
			2.f);

		if (HitResult.bBlockingHit)
		{
			SelectedMovableActor = Cast<AMovableActorBase>(HitResult.Component->GetOwner());
			if (SelectedMovableActor.IsValid())
			{
				// 显示控制手柄
				SelectedMovableActor->SetControlHandleVisible(true, eTranformMode);
				
				LastSelectedMovableActor = SelectedMovableActor;
				
			}
		}
		else
		{
			SelectedMovableActor = nullptr;
		}
		return;
	}

	SelectedMovableActor = nullptr;
	return ;
}

FVector2D UMovableInteractionComponent::GetHandleSizeOnScreen()
{
	if (!SelectedMovableActor.IsValid())
		return FVector2D{0.f, 0.f};

	// 获取初始 BoundingBox
	FVector Orig = SelectedMovableActor->DefaultBound_Orig;
	FVector Extend = SelectedMovableActor->DefaultBound_Extend;

	TArray<FVector> BoundBoxVertices = {
		Orig + FVector{Extend.X, Extend.Y, Extend.Z},
		Orig + FVector{-Extend.X, Extend.Y, Extend.Z},
		Orig + FVector{Extend.X, -Extend.Y, Extend.Z},
		Orig + FVector{-Extend.X, -Extend.Y, Extend.Z},

		Orig + FVector{Extend.X, Extend.Y, -Extend.Z},
		Orig + FVector{-Extend.X, Extend.Y, -Extend.Z},
		Orig + FVector{Extend.X, -Extend.Y, -Extend.Z},
		Orig + FVector{-Extend.X, -Extend.Y, -Extend.Z},
	};

	FVector2D ScreenSize;
	GEngine->GameViewport->GetViewportSize(ScreenSize);

	FVector2D MinScreenPos{ScreenSize.X, ScreenSize.Y};
	FVector2D MaxScreenPos{0, 0};
	
	for (FVector& WorldPos:BoundBoxVertices)
	{
		FVector2D ScreenPos;
		PlayerPC->ProjectWorldLocationToScreen(WorldPos, ScreenPos);
		ScreenPos = FVector2D{FMath::Clamp(ScreenPos.X, 0.f, ScreenSize.X), FMath::Clamp(ScreenPos.Y, 0.f, ScreenSize.Y)};

		MinScreenPos.X = FMath::Min(MinScreenPos.X, ScreenPos.X);
		MinScreenPos.Y = FMath::Min(MinScreenPos.Y, ScreenPos.Y);
		
		MaxScreenPos.X = FMath::Max(MaxScreenPos.X, ScreenPos.X);
		MaxScreenPos.Y = FMath::Max(MaxScreenPos.Y, ScreenPos.Y);
	}
	
	return MaxScreenPos - MinScreenPos;
}

FVector2D UMovableInteractionComponent::BP_GetHandleSizeOnScreen()
{
	return GetHandleSizeOnScreen();
}

void UMovableInteractionComponent::UpdateHandleSizeOnScreen()
{
	if (!SelectedMovableActor.IsValid())
		return;

	// 获取当前屏幕大小
	FVector2D ScreenSize;
	GEngine->GameViewport->GetViewportSize(ScreenSize);
	// 获取当前控制手柄占屏幕空间大小
	float CurrentSize = GetHandleSizeOnScreen().Length() / ScreenSize.Length();
	// 设置手柄在世界空间的缩放比率
	float ActualSize = ControlHandleSizeOnScreen / CurrentSize;
	
	SelectedMovableActor->Grp_Transform->SetWorldScale3D(ActualSize * FVector{1.f, 1.f, 1.f});
}

void UMovableInteractionComponent::DetectHoveredHandle()
{
	if (LastHoverCmp.IsValid())
		SelectedMovableActor->ResetHandleState(LastHoverCmp.Get());
	
	FVector MouseWorldPos, MouseWorldDir;
	if (GetMouseWorldPositionDirection(MouseWorldPos, MouseWorldDir))
	{
		TArray<FHitResult> HitResults;
		UKismetSystemLibrary::LineTraceMultiForObjects(
			GetWorld(),
			MouseWorldPos,
			MouseWorldPos + DetectDistance*MouseWorldDir,
			TArray<TEnumAsByte<EObjectTypeQuery>>{EObjectTypeQuery::ObjectTypeQuery2},
			false,
			TArray<AActor*>{GetOwner()},
			EDrawDebugTrace::None,
			HitResults,
			true,
			FLinearColor::Red,
			FLinearColor::Green,
			2.f);

		TArray<UStaticMeshComponent*> FirstHandles;
		TArray<UStaticMeshComponent*> SecondHandles;
		TArray<UStaticMeshComponent*> ThirdHandles;
		for (FHitResult& HitResult:HitResults)
		{
			if (HitResult.bBlockingHit)
			{
				AMovableActorBase* MovableActor = Cast<AMovableActorBase>(HitResult.Component->GetOwner());
				if (MovableActor)
				{
					UStaticMeshComponent* Handle = Cast<UStaticMeshComponent>(HitResult.Component);
					if (MovableActor->IsControlHandle(Handle))
					{
						if (MovableActor->GetHandleTranformMode(Handle) == eTranformMode)
						{
							switch (MovableActor->GetHandlePriority(Handle))
							{
							case EHandlePriority::EHP_First:
								FirstHandles.Add(Handle);
								break;

							case EHandlePriority::EHP_Second:
								SecondHandles.Add(Handle);
								break;

							case EHandlePriority::EHP_Third:
								ThirdHandles.Add(Handle);
								break;
							}
						}
					}
				}
			}

		}

		LastHoverCmp = CurrentHoverCmp;
		if (!FirstHandles.IsEmpty())
		{
			CurrentHoverCmp = FirstHandles[0];
		}
		else if (!SecondHandles.IsEmpty())
		{
			CurrentHoverCmp = SecondHandles[0];
		}
		else if (!ThirdHandles.IsEmpty())
		{
			CurrentHoverCmp = ThirdHandles[0];
		}
		else
		{
			CurrentHoverCmp = nullptr;
		}

		if (CurrentHoverCmp.IsValid())
		{
			AMovableActorBase* MovableActor = Cast<AMovableActorBase>(CurrentHoverCmp->GetOwner());
			MovableActor->SetHandleStateHover(CurrentHoverCmp.Get());
		}
	}
}

void UMovableInteractionComponent::OnDragBegin()
{
	if (SelectedMovableActor.IsValid())
	{
		CurrentHoverCmp = nullptr;
		DetectHoveredHandle();
		if (!CurrentHoverCmp.IsValid())
		{
			bIsDragging = false;
			return;
		}
		
		bIsDragging = true;

		// 更新约束平面
		UpdateConstraintPlane();
		// 计算开始拖拽时，鼠标在约束平面上的位置
		StartDragPos = GetMousePosOnConstraintPlane();
		LastDragPos = StartDragPos;
		// 保存开始拖拽时，Actor 的世界位置
		StartActorPos = SelectedMovableActor->GetActorLocation();
		// 保存开始拖拽时，Actor 的世界旋转
		StartActorRotator = SelectedMovableActor->GetActorRotation();
		// 保存开始拖拽时，Actor 的世界缩放
		StartActorScale = SelectedMovableActor->GetActorScale3D();
		GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Silver,
		FString::Printf(TEXT("{%f, %f, %f}"),StartActorScale.X, StartActorScale.Y, StartActorScale.Z));
	}
}

void UMovableInteractionComponent::OnDragEnd()
{
	bIsDragging = false;
	DraggedAngle = 0.f;
}

void UMovableInteractionComponent::ToggleTransformMode(ETransformMode TransformMode)
{
	eTranformMode = TransformMode;
	if (SelectedMovableActor.IsValid())
	{
		SelectedMovableActor->SetControlHandleVisible(false, ETransformMode::ETM_Translation);
		SelectedMovableActor->SetControlHandleVisible(false, ETransformMode::ETM_Rotation);
		SelectedMovableActor->SetControlHandleVisible(false, ETransformMode::ETM_Scale);
		
		SelectedMovableActor->SetControlHandleVisible(true, eTranformMode);
	}
}

bool UMovableInteractionComponent::GetMouseWorldPositionDirection(FVector& WorldPos, FVector& WorldDirection)
{
	// 获取鼠标世界位置、方向
	double LocationX, LocationY;
	PlayerPC->GetMousePosition(LocationX, LocationY);

	return PlayerPC->DeprojectMousePositionToWorld(WorldPos, WorldDirection);
}

void UMovableInteractionComponent::UpdateConstraintPlane()
{
	FVector ViewLocation;
	FRotator ViewRotator;
	PlayerPC->GetPlayerViewPoint(ViewLocation, ViewRotator);
	
	FVector ActorLocation = SelectedMovableActor->GetActorLocation();
	FVector ActorForward = SelectedMovableActor->GetActorForwardVector();
	FVector ActorRight = SelectedMovableActor->GetActorRightVector();
	FVector ActorUp = SelectedMovableActor->GetActorUpVector();

	auto GetPlaneNormal = [&](const FVector& FirstVec)->FVector
	{
		FVector ToViewVec = ActorLocation - ViewLocation;
		FVector SecondVec = FirstVec.Cross(ToViewVec);
		FVector Normal = FirstVec.Cross(SecondVec);

		return Normal.GetSafeNormal();
	};
	
	// 对齐方向。如果 Handle 是单一轴向，则对齐方向与该轴方向平行。
	// 如果 Handle 是一个平面，如 XY,则 对齐方向为该平面的法线方向。
	FVector AlignVec;
	FVector Normal{};
	// 计算约束平面。位移只能在该平面内
	switch (SelectedMovableActor->GetHandleTranformAxes(CurrentHoverCmp.Get()))
	{
	case ETransformAxes::ECA_XYZ:
	case ETransformAxes::ECA_Org:
		ConstraintPlane = FPlane{ActorLocation, (ViewLocation - StartActorPos).GetSafeNormal()};
		break;

	case ETransformAxes::ECA_X:
		if (SelectedMovableActor->GetHandleTranformMode(CurrentHoverCmp.Get()) == ETransformMode::ETM_Rotation)
		{
			ConstraintPlane = FPlane{ActorLocation, ActorForward};
		}
		else
		{
			AlignVec = ActorForward;
			Normal = GetPlaneNormal(AlignVec);
			ConstraintPlane = FPlane{ActorLocation, Normal};
		}
		break;

	case ETransformAxes::ECA_Y:
		if (SelectedMovableActor->GetHandleTranformMode(CurrentHoverCmp.Get()) == ETransformMode::ETM_Rotation)
		{
			ConstraintPlane = FPlane{ActorLocation, ActorRight};
		}
		else
		{
			AlignVec = ActorRight;
			Normal = GetPlaneNormal(AlignVec);
			ConstraintPlane = FPlane{ActorLocation, Normal};
		}
		break;

	case ETransformAxes::ECA_Z:
		if (SelectedMovableActor->GetHandleTranformMode(CurrentHoverCmp.Get()) == ETransformMode::ETM_Rotation)
		{
			ConstraintPlane = FPlane{ActorLocation, ActorUp};
		}
		else
		{
			AlignVec = ActorUp;
			Normal = GetPlaneNormal(AlignVec);
			ConstraintPlane = FPlane{ActorLocation, Normal};
		}
		break;

	case ETransformAxes::ECA_XY:
		AlignVec = ActorUp;
		ConstraintPlane = FPlane{ActorLocation, AlignVec};
		break;

	case ETransformAxes::ECA_XZ:
		AlignVec = ActorRight;
		ConstraintPlane = FPlane{ActorLocation, AlignVec};
		break;

	case ETransformAxes::ECA_YZ:
		AlignVec = ActorForward;
		ConstraintPlane = FPlane{ActorLocation, AlignVec};
		break;
	}
}

FVector UMovableInteractionComponent::GetMousePosOnConstraintPlane()
{
	FVector MouseWorldPos, MouseWorldDir;
	if (GetMouseWorldPositionDirection(MouseWorldPos, MouseWorldDir))
	{
		FVector IntersectionPos;
		float OutT;
		if (UKismetMathLibrary::LinePlaneIntersection(MouseWorldPos, MouseWorldPos+DetectDistance*MouseWorldDir, ConstraintPlane,
			OutT, IntersectionPos))
			return IntersectionPos;
	}
	
	return MouseWorldPos;
}

void UMovableInteractionComponent::DoTranslate()
{

	FVector MousePosOnPlane = GetMousePosOnConstraintPlane();
	// 计算鼠标世界位置是否在约束平面上
	double PointToPlaneDis = ConstraintPlane.PlaneDot(MousePosOnPlane);
	if (!(-0.01f < PointToPlaneDis && PointToPlaneDis < 0.01f))
		return;

	// Debug
	/*DrawDebugSolidPlane(GetWorld(), ConstraintPlane, SelectedMovableActor->GetActorLocation(), 200.f, FColor::Red);
	DrawDebugPoint(GetWorld(), MousePosOnPlane, 10.f, FColor::Blue);*/
	// 获取移动轴
	FVector MoveDir = MousePosOnPlane - StartDragPos;
	FVector ActualDir;
	FVector ActorForward = SelectedMovableActor->GetActorForwardVector();
	FVector ActorRight = SelectedMovableActor->GetActorRightVector();
	FVector ActorUp = SelectedMovableActor->GetActorUpVector();
	
	switch (SelectedMovableActor->GetHandleTranformAxes(CurrentHoverCmp.Get()))
	{
	case ETransformAxes::ECA_Org:
		ActualDir = MoveDir;
		break;

	case ETransformAxes::ECA_X:
		ActualDir = ActorForward * MoveDir.Dot(ActorForward);
		break;

	case ETransformAxes::ECA_Y:
		ActualDir = ActorRight * MoveDir.Dot(ActorRight);
		break;

	case ETransformAxes::ECA_Z:
		ActualDir = ActorUp * MoveDir.Dot(ActorUp);
		break;

	case ETransformAxes::ECA_XY:
		ActualDir = MoveDir;
		break;

	case ETransformAxes::ECA_XZ:
		ActualDir = MoveDir;
		break;

	case ETransformAxes::ECA_YZ:
		ActualDir = MoveDir;
		break;
	}
	
	SelectedMovableActor->SetActorLocation(StartActorPos + ActualDir);
}

void UMovableInteractionComponent::DoRotate()
{
	FVector MousePosOnPlane = GetMousePosOnConstraintPlane();
	// 计算鼠标世界位置是否在约束平面上
	double PointToPlaneDis = ConstraintPlane.PlaneDot(MousePosOnPlane);
	if (!(-0.01f < PointToPlaneDis && PointToPlaneDis < 0.01f))
		return;
	
	FVector LastDir = LastDragPos - StartActorPos;
	FVector CurrentDir = MousePosOnPlane - StartActorPos;
	FVector DragDir = MousePosOnPlane - StartDragPos;
	
	FVector ActorForward = UKismetMathLibrary::GetForwardVector(StartActorRotator);
	FVector ActorRight = UKismetMathLibrary::GetRightVector(StartActorRotator);
	FVector ActorUp = UKismetMathLibrary::GetUpVector(StartActorRotator);

	FVector ViewLocation;
	FRotator ViewRotator;
	PlayerPC->GetPlayerViewPoint(ViewLocation, ViewRotator);
	FVector ViewDir = ViewLocation - StartActorPos;
	ViewDir.Normalize();
	
	FVector ActualRotator;
	FVector RotatorAxes;
	
	switch (SelectedMovableActor->GetHandleTranformAxes(CurrentHoverCmp.Get()))
	{
	case ETransformAxes::ECA_Org:
		RotatorAxes = ViewDir;
		break;

	case ETransformAxes::ECA_X:
		RotatorAxes = ActorForward;
		break;

	case ETransformAxes::ECA_Y:
		RotatorAxes = ActorRight;
		break;

	case ETransformAxes::ECA_Z:
		RotatorAxes = ActorUp;
		break;

	case ETransformAxes::ECA_XYZ:
		RotatorAxes = DragDir.Cross(ViewDir);
		break;
	}
	RotatorAxes.Normalize();

	// Debug
	/*DrawDebugSolidPlane(GetWorld(), ConstraintPlane, SelectedMovableActor->GetActorLocation(), 200.f, FColor::Red);
	DrawDebugPoint(GetWorld(), MousePosOnPlane, 10.f, FColor::Blue);
	DrawDebugDirectionalArrow(GetWorld(), StartActorPos, StartActorPos+300.f*RotatorAxes, 10.f, FColor::White);*/
	
	// 计算旋转角
	if (SelectedMovableActor->GetHandleTranformAxes(CurrentHoverCmp.Get()) == ETransformAxes::ECA_XYZ)
	{
		DraggedAngle = RotatorAxes.Cross(ViewDir).GetSafeNormal().Dot(DragDir);
	}
	else
	{
		double BetweenAngle = UKismetMathLibrary::DegAcos(LastDir.GetSafeNormal().Dot(CurrentDir.GetSafeNormal())) * RotationSpeed;
		float Sign = FMath::Sign(LastDir.Cross(CurrentDir).Dot(RotatorAxes));
		BetweenAngle *= Sign;
		DraggedAngle += BetweenAngle;
	}
	
	// 计算旋转
	FRotator DeltaRotator = UKismetMathLibrary::RotatorFromAxisAndAngle(RotatorAxes, DraggedAngle);
	SelectedMovableActor->SetActorRotation(UKismetMathLibrary::ComposeRotators(StartActorRotator, DeltaRotator));
	
	LastDragPos = MousePosOnPlane;
}

void UMovableInteractionComponent::DoScale()
{
	FVector MousePosOnPlane = GetMousePosOnConstraintPlane();
	// 计算鼠标世界位置是否在约束平面上
	double PointToPlaneDis = ConstraintPlane.PlaneDot(MousePosOnPlane);
	if (!(-0.01f < PointToPlaneDis && PointToPlaneDis < 0.01f))
		return;

	FVector ActorForward = SelectedMovableActor->GetActorForwardVector();
	FVector ActorRight = SelectedMovableActor->GetActorRightVector();
	FVector ActorUp = SelectedMovableActor->GetActorUpVector();

	FVector DragDir = MousePosOnPlane - StartDragPos;
	
	// Debug
	/*DrawDebugSolidPlane(GetWorld(), ConstraintPlane, SelectedMovableActor->GetActorLocation(), 200.f, FColor::Red);
	DrawDebugPoint(GetWorld(), MousePosOnPlane, 10.f, FColor::Blue);*/

	FVector ScaleAxes;
	switch (SelectedMovableActor->GetHandleTranformAxes(CurrentHoverCmp.Get()))
	{
	case ETransformAxes::ECA_Org:
		ScaleAxes = FVector{1.f, 1.f, 1.f};
		break;

	case ETransformAxes::ECA_X:
		ScaleAxes = FVector{1.f, 0.f, 0.f};
		break;

	case ETransformAxes::ECA_Y:
		ScaleAxes = FVector{0.f, 1.f, 0.f};
		break;

	case ETransformAxes::ECA_Z:
		ScaleAxes = FVector{0.f, 0.f, 1.f};
		break;

	case ETransformAxes::ECA_XY:
		ScaleAxes = FVector{1.f, 1.f, 0.f};
		break;

	case ETransformAxes::ECA_XZ:
		ScaleAxes = FVector{1.f, 0.f, 1.f};
		break;

	case ETransformAxes::ECA_YZ:
		ScaleAxes = FVector{0.f, 1.f, 1.f};
		break;
	}

	float ScaleRatio = DragDir.Dot(ScaleAxes) * ScaleSpeed;
	FVector OutScale = StartActorScale + ScaleAxes * ScaleRatio;
	/*GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Silver,
		FString::Printf(TEXT("{%f, %f, %f}"),OutScale.X, OutScale.Y, OutScale.Z));
	OutScale = SelectedMovableActor->GetTransform().TransformVector(OutScale);*/

	SelectedMovableActor->SetActorScale3D(OutScale);
}
